Skip to content

feat(agent-server): add seatbelt option for macOS sandbox-exec#3186

Draft
rbren wants to merge 1 commit into
mainfrom
feat/seatbelt-conversation-option
Draft

feat(agent-server): add seatbelt option for macOS sandbox-exec#3186
rbren wants to merge 1 commit into
mainfrom
feat/seatbelt-conversation-option

Conversation

@rbren

@rbren rbren commented May 10, 2026

Copy link
Copy Markdown
Member

Adds a seatbelt boolean option to the conversation creation API. When true, the agent-server validates that the host is macOS with sandbox-exec available (returning HTTP 400 otherwise) and threads the flag through LocalConversation -> ConversationState -> TerminalTool, so shell processes spawned by the conversation are wrapped with sandbox-exec -p <profile>.

The default profile permits arbitrary reads, network, and fork/exec, but restricts writes to the workspace and standard temp directories.

  • New openhands.sdk.utils.seatbelt helper (availability check, default profile, argv wrapper)
  • Plumbed through StartConversationRequest, StartACPConversationRequest, Conversation/LocalConversation/RemoteConversation, ConversationState, EventService, TerminalTool, TerminalExecutor, and both terminal backends (subprocess + tmux, including the tmux pane pool)
  • Tests for the helper, terminal wiring, and the new router validation

Fixes #1999

  • A human has tested these changes.

Why

Summary

Issue Number

How to Test

Video/Screenshots

Type

  • Bug fix
  • Feature
  • Refactor
  • Breaking change
  • Docs / chore

Notes


Agent Server images for this PR

GHCR package: https://github.com/OpenHands/agent-sdk/pkgs/container/agent-server

Variants & Base Images

Variant Architectures Base Image Docs / Tags
java amd64, arm64 eclipse-temurin:17-jdk Link
python amd64, arm64 nikolaik/python-nodejs:python3.13-nodejs22-slim Link
golang amd64, arm64 golang:1.21-bookworm Link

Pull (multi-arch manifest)

# Each variant is a multi-arch manifest supporting both amd64 and arm64
docker pull ghcr.io/openhands/agent-server:2a84be5-python

Run

docker run -it --rm \
  -p 8000:8000 \
  --name agent-server-2a84be5-python \
  ghcr.io/openhands/agent-server:2a84be5-python

All tags pushed for this build

ghcr.io/openhands/agent-server:2a84be5-golang-amd64
ghcr.io/openhands/agent-server:2a84be5d189e2e9329cf945c006236e4ad9f534e-golang-amd64
ghcr.io/openhands/agent-server:feat-seatbelt-conversation-option-golang-amd64
ghcr.io/openhands/agent-server:2a84be5-golang_tag_1.21-bookworm-amd64
ghcr.io/openhands/agent-server:2a84be5-golang-arm64
ghcr.io/openhands/agent-server:2a84be5d189e2e9329cf945c006236e4ad9f534e-golang-arm64
ghcr.io/openhands/agent-server:feat-seatbelt-conversation-option-golang-arm64
ghcr.io/openhands/agent-server:2a84be5-golang_tag_1.21-bookworm-arm64
ghcr.io/openhands/agent-server:2a84be5-java-amd64
ghcr.io/openhands/agent-server:2a84be5d189e2e9329cf945c006236e4ad9f534e-java-amd64
ghcr.io/openhands/agent-server:feat-seatbelt-conversation-option-java-amd64
ghcr.io/openhands/agent-server:2a84be5-eclipse-temurin_tag_17-jdk-amd64
ghcr.io/openhands/agent-server:2a84be5-java-arm64
ghcr.io/openhands/agent-server:2a84be5d189e2e9329cf945c006236e4ad9f534e-java-arm64
ghcr.io/openhands/agent-server:feat-seatbelt-conversation-option-java-arm64
ghcr.io/openhands/agent-server:2a84be5-eclipse-temurin_tag_17-jdk-arm64
ghcr.io/openhands/agent-server:2a84be5-python-amd64
ghcr.io/openhands/agent-server:2a84be5d189e2e9329cf945c006236e4ad9f534e-python-amd64
ghcr.io/openhands/agent-server:feat-seatbelt-conversation-option-python-amd64
ghcr.io/openhands/agent-server:2a84be5-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim-amd64
ghcr.io/openhands/agent-server:2a84be5-python-arm64
ghcr.io/openhands/agent-server:2a84be5d189e2e9329cf945c006236e4ad9f534e-python-arm64
ghcr.io/openhands/agent-server:feat-seatbelt-conversation-option-python-arm64
ghcr.io/openhands/agent-server:2a84be5-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim-arm64
ghcr.io/openhands/agent-server:2a84be5-golang
ghcr.io/openhands/agent-server:2a84be5d189e2e9329cf945c006236e4ad9f534e-golang
ghcr.io/openhands/agent-server:feat-seatbelt-conversation-option-golang
ghcr.io/openhands/agent-server:2a84be5-golang_tag_1.21-bookworm
ghcr.io/openhands/agent-server:2a84be5-java
ghcr.io/openhands/agent-server:2a84be5d189e2e9329cf945c006236e4ad9f534e-java
ghcr.io/openhands/agent-server:feat-seatbelt-conversation-option-java
ghcr.io/openhands/agent-server:2a84be5-eclipse-temurin_tag_17-jdk
ghcr.io/openhands/agent-server:2a84be5-python
ghcr.io/openhands/agent-server:2a84be5d189e2e9329cf945c006236e4ad9f534e-python
ghcr.io/openhands/agent-server:feat-seatbelt-conversation-option-python
ghcr.io/openhands/agent-server:2a84be5-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim

About Multi-Architecture Support

  • Each variant tag (e.g., 2a84be5-python) is a multi-arch manifest supporting both amd64 and arm64
  • Docker automatically pulls the correct architecture for your platform
  • Individual architecture tags (e.g., 2a84be5-python-amd64) are also available if needed

…xing

Adds a `seatbelt` boolean option to the conversation creation API. When
true, the agent-server validates that the host is macOS with
`sandbox-exec` available (returning HTTP 400 otherwise) and threads the
flag through LocalConversation -> ConversationState -> TerminalTool, so
shell processes spawned by the conversation are wrapped with
`sandbox-exec -p <profile>`.

The default profile permits arbitrary reads, network, and fork/exec, but
restricts writes to the workspace and standard temp directories.

- New `openhands.sdk.utils.seatbelt` helper (availability check, default
  profile, argv wrapper)
- Plumbed through StartConversationRequest, StartACPConversationRequest,
  Conversation/LocalConversation/RemoteConversation, ConversationState,
  EventService, TerminalTool, TerminalExecutor, and both terminal
  backends (subprocess + tmux, including the tmux pane pool)
- Tests for the helper, terminal wiring, and the new router validation

Co-authored-by: openhands <openhands@all-hands.dev>
@github-actions

Copy link
Copy Markdown
Contributor

Python API breakage checks — ✅ PASSED

Result:PASSED

Action log

@github-actions

Copy link
Copy Markdown
Contributor

REST API breakage checks (OpenAPI) — ✅ PASSED

Result:PASSED

Action log

@github-actions

Copy link
Copy Markdown
Contributor

Coverage

Coverage Report •
FileStmtsMissCoverMissing
openhands-agent-server/openhands/agent_server
   conversation_router.py1571093%326, 417–420, 432–435, 469
   conversation_router_acp.py39197%108
   event_service.py4379678%75–76, 101, 105, 108–109, 113–114, 121, 131–135, 138–141, 161, 265, 282, 323, 333, 357–358, 362, 370, 373, 414, 430, 432, 436–438, 442, 451–452, 454, 458, 464, 466, 496–501, 527, 530, 581, 585, 732, 734–735, 739, 753–755, 757, 761–764, 768–771, 779–782, 830–831, 833–840, 842–843, 852–853, 855–856, 863–864, 866–867, 887, 893, 899, 908–909
openhands-sdk/openhands/sdk/conversation
   conversation.py34876%143, 156–157, 163–166, 170
   request.py47197%60
   state.py1922487%268, 355, 387, 435–437, 453–454, 460, 466–469, 473, 479–482, 512, 522, 540, 549, 564, 570
openhands-sdk/openhands/sdk/conversation/impl
   local_conversation.py44719755%215, 217–218, 264, 300, 305, 315, 346, 353–355, 361, 366–370, 373, 388–389, 395, 398, 401–402, 408–409, 411, 413, 418, 436, 449–450, 452, 454, 461–462, 465–466, 472–474, 477–478, 481–482, 484, 495, 501, 506, 514–515, 519, 528–531, 535, 600, 649–657, 673–679, 784, 796–799, 806–807, 810, 819–820, 823, 830, 851, 857, 861–862, 866–868, 875, 905, 907, 909, 913, 915–917, 919, 921, 927–928, 941–942, 944, 946, 950–953, 983, 991, 993, 997–998, 1009, 1011–1013, 1033, 1036–1038, 1041, 1043, 1047, 1052, 1057, 1062–1065, 1071, 1074, 1078, 1081, 1083–1085, 1087, 1108–1109, 1123, 1127, 1135, 1151–1152, 1156, 1158, 1160, 1199, 1202–1207, 1209, 1211–1214, 1216, 1218–1219, 1222–1225, 1232–1233, 1237, 1240–1242, 1245, 1247, 1249, 1256–1258, 1262, 1264–1265, 1295, 1298–1301, 1306–1308, 1314–1315
   remote_conversation.py65722166%67, 74–75, 77–78, 104–106, 137, 144, 168, 171, 178–179, 184, 186–189, 199, 221–222, 227–230, 273, 287, 314, 324–326, 332, 355–362, 365, 369, 372, 378–379, 390, 406, 426–427, 468, 494, 514, 522, 534, 542–545, 548, 553–556, 558, 563–564, 575–579, 584–588, 593–596, 599, 611–615, 619–620, 624, 628, 631, 724–725, 731–732, 734, 777–778, 782–783, 786, 797, 823–824, 829, 831–832, 843, 854–855, 875–878, 880–881, 894–895, 905–907, 910–914, 916–917, 921, 923–931, 933, 952, 970, 1016, 1018, 1021, 1067, 1099–1101, 1130, 1146, 1148–1149, 1154, 1162–1166, 1173–1174, 1178, 1183–1187, 1191–1199, 1202–1203, 1212, 1217, 1226, 1237, 1244, 1246–1248, 1252, 1255–1256, 1258, 1260–1261, 1284, 1286, 1292–1293, 1309, 1311–1312, 1329, 1367–1368, 1373–1379, 1381, 1387–1388, 1390–1391, 1397, 1399, 1423, 1446–1447, 1465–1466
openhands-sdk/openhands/sdk/utils
   seatbelt.py191142%27–29, 33, 35, 49–50, 54, 91–93
openhands-tools/openhands/tools/terminal
   definition.py1115352%70, 73, 76–77, 79, 82–84, 86–88, 90–92, 94, 122, 130, 157, 159–161, 164, 166, 168–170, 172, 176–177, 180–182, 184–185, 188–191, 195–197, 202, 206–211, 213–214, 216, 231, 265
   impl.py2319359%116, 124–125, 154–158, 161–163, 165–169, 176, 195–196, 201–202, 231–234, 236, 254–263, 265, 270, 276–277, 300, 303, 306, 310, 328, 331, 339–340, 349, 382–383, 385, 393, 397–400, 402–403, 411, 413, 417, 441–442, 444–446, 451–452, 454–455, 457, 466, 468–469, 471, 491–494, 499–500, 522, 532–535, 539–540, 548, 553, 561–562
openhands-tools/openhands/tools/terminal/terminal
   factory.py735721%27–28, 33–35, 37, 39–43, 50–54, 59, 68, 70–72, 74–75, 105–109, 111–113, 115–117, 124–126, 131, 137, 141–142, 145, 147, 149–151, 158–159, 161–163, 165, 169, 174–177
   subprocess_terminal.py28123217%15, 62–63, 89–92, 95–100, 106–107, 113–114, 116–118, 125–126, 130–131, 137–138, 141–144, 146, 151–152, 154–155, 158, 160–162, 176–179, 181, 184–185, 188, 191–192, 195, 199–200, 202, 204, 208–209, 211–212, 214–220, 222–228, 231–236, 238–239, 241–242, 247, 249–257, 261–263, 265–266, 268–269, 272–274, 276–279, 281–282, 284–285, 287–292, 297–299, 301, 304, 307–308, 311–312, 318–320, 322–329, 331–334, 338–346, 381–382, 384–385, 388–389, 391, 393, 395–397, 400–403, 405–407, 409–410, 412–413, 422–425, 434–436, 439–440, 443, 445, 448–449, 451, 458–459, 463, 465–470, 474–475, 477–480, 482–488, 490–491, 493–497, 501–502, 504–510, 514–515, 518–519, 521–522, 524–526
   tmux_pane_pool.py1443079%51–53, 111, 117, 147, 162–163, 171, 225, 229–230, 250–251, 267, 270–271, 273–276, 279–280, 284–288, 290–291
   tmux_terminal.py1086044%56–57, 85–86, 88, 90–92, 94, 96, 100–102, 109–110, 114–115, 118–119, 124–128, 132, 135, 137–139, 143–148, 152–153, 170, 177–178, 183–184, 199, 221, 233–240, 248–249, 251–252, 254, 256–258
TOTAL268611183955% 

@all-hands-bot

Copy link
Copy Markdown
Collaborator

[Automatic Post]: It has been a while since there was any activity on this PR. @rbren, are you still working on it? If so, please go ahead, if not then please request review, close it, or request that someone else follow up.

This comment was generated by an AI agent (OpenHands) on behalf of the user.

@enyst enyst changed the title feat(agent-server): add seatbelt option for macOS sandbox-exec sandbo… feat(agent-server): add seatbelt option for macOS sandbox-exec May 25, 2026
@all-hands-bot

Copy link
Copy Markdown
Collaborator

[Automatic Post]: It has been a while since there was any activity on this PR. @rbren, are you still working on it? If so, please go ahead, if not then please request review, close it, or request that someone else follow up.

This comment was created by an AI agent (OpenHands) on behalf of the user.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support MacOS seatbelt workspace?

3 participants